home *** CD-ROM | disk | FTP | other *** search
/ Mac Magazin/MacEasy 32 / Mac Magazin and MacEasy Magazine CD - Issue 32.iso / Grafik & Text / OzTeX3.0 / MetaPost / Inputs / graph.mp < prev    next >
Text File  |  1996-08-24  |  27KB  |  831 lines

  1. % Macros for drawing graphs
  2.  
  3. % begingraph(width,height)       begin a new graph
  4. % setcoords(xtype,ytype)         sets up a new coordinate system (log,-linear..)
  5. % setrange(lo,hi)                set coord ranges (numeric and string args OK)
  6. % gdraw <file or path> [with...] draw a line in current coord system
  7. % gfill <file or path> [with...] fill a region using current coord system
  8. % gdrawarrow .., gdrawdblarrow.. like gdraw, but with 1 or 2 arrowheads
  9. % Mreadpath(<filename>)          read path from file and return it in Mlog form
  10. % augment<path name>(loc)        append given coordinates to a polygonal path
  11. % glabel<suffix>(pic,loc)        place label pic near graph coords or time loc
  12. % gdotlabel<suffix>(pic,loc)     same with dot
  13. % OUT                            loc value for labels relative to whole graph
  14. % gdata(file,s,text)             read coords from file; evaluate t w/ tokens s[]
  15. % auto.<x or y>                  default x or y tick locations (for interation)
  16. % itick.<bot|top|..>(fmt,u)      draw inward tick from given side at u w/ format
  17. % otick.<bot|top|..>(fmt,u)      draw outward tick at coord u; label format fmt
  18. % grid.<bot|top|..>(fmt,u)       draw grid line at u with given side labelled
  19. % autogrid([itick|.. bot|..],..) iterate over auto.x, auto.y, drawing tick/grids
  20. % frame.[bot|top..]              draw frame (or one side of the frame)
  21. % endgraph                       end of graph--the result is a picture
  22.  
  23. % option `plot <picture>'        draws picture at each path knot, turns off pen
  24. % Gtemplate.<tickcmd>            template paths for tick marks and grid lines
  25. % Gmargin.low, Gmargin.high      fractions determining margins when no setrange
  26. % Glmarks[], Gumarks, Gemarks    loop text strings used by auto.<x or y>
  27. % Gmarks, Gminlog                numeric parameters used by auto.<x or y>
  28. % Gpaths                         tells how to interpret paths: log or linear
  29. % Autoform                       is the format string used by autogrid
  30.  
  31. % Other than the above-documented user interface, all externally visible names
  32. % are of the form X_.<suffix>, Y_.<suffix>, or Z_.<suffix>, or they start
  33. % with `G' and end with `_'.
  34.  
  35.  
  36. if unknown Mzero:
  37.   begingroup interim    % marith.mp starts with `warningcheck:=0'
  38.   input marith
  39.   endgroup;             % restore warningcheck; we zero it when necessary
  40. fi
  41. if unknown mant_font:
  42.   input format
  43. fi
  44.  
  45.  
  46. vardef Gerr_(expr x,s) =
  47.   interim showstopping:=0;
  48.   show x; errmessage s;
  49. enddef;
  50.  
  51.  
  52.  
  53. %%%%%%%%%%%%%%%%%%%%%%%% Data structures, begingraph %%%%%%%%%%%%%%%%%%%%%%%%
  54.  
  55. vardef Z_@# = (X_@#,Y_@#) enddef; % used in place of plain.mp's z convention
  56.  
  57. def Gsuf_(suffix $) =             % convert from x or y to X_ or Y_
  58.   if str$="x": X_ else: Y_ fi
  59. enddef;
  60.  
  61.  
  62. def begingraph(expr w, h) =
  63.   begingroup
  64.   save X_, Y_, Gfin_, Gcur_, Gcbb_, Gneedgr_, Gneedfr_, Gdidsc_;
  65.   save Gdpic_, Gppic_, Ggl_, Garw_;
  66.   picture Gfin_, Gcur_, Gcbb_, Gdpic_, Gppic_, Ggl_[];
  67.   boolean Gneedgr_, Gneedfr_, Gdidsc_;
  68.   Gfin_ = nullpicture;          % the finished part of the graph
  69.   Gcur_ = nullpicture;          % what has been drawn in current coords
  70.   Gcbb_ = nullpicture;          % picture whose bbox is Gcur_'s w/ linewidths 0
  71.   X_.ctyp = Y_.ctyp = linear;   % coordinate system for each axis
  72.   Z_.gdim = (w,h);              % dimensions of graph not counting axes etc.
  73.   X_.sc = Y_.sc = 0;            % Mlog(the amount Gcur_ has been descaled by)
  74.   Gneedgr_ = true;              % whether autogrid is needed
  75.   Gneedfr_ = true;              % whether frame needs to be drawn
  76.   Gdidsc_ = false;              % set when Glinsc_ rescales coordinates
  77.   Gdpic_ = nullpicture;         % result of last gdraw or gfill
  78.   Garw_ = 0;                    % number of arrowheads for next gdraw
  79. enddef;
  80.  
  81. % Additional variables not explained above:
  82. % Z_.low, Z_.high       user-specified coordinate ranges in units used in Gcur_
  83. % Gppic_                a picture from the `plot' option known when plot allowed
  84. % Gmll_, Gmhh_          pairs giving bounds used in auto<x or y>
  85. % Gme_, Gcma_           variables and macros used in auto<x or y>
  86. % Gc_                   temporary macro used in auto<x or y>
  87. % Gbias_                an offset to Gmll_ and Gmhh_ to ease computing exponents
  88. % Ggl_[]                labels to place around the whole graph when it is done
  89. % Some additional variables function as constants.  Most can be modified by the
  90. % user to alter the behavior of these macros.
  91. % Not very modifiable:  log, linear, Gboff_, Gfra_, Gfrb_, Gmarg_
  92. % Modifiable:           Gtemplate.suffix, Glmarks[], Gumarks, Gemarks, Gmarks,
  93. %                       Gminlog, Gpaths, Autoform
  94.  
  95.  
  96. newinternal log, linear;        % coordinate system codes
  97. newinternal Gpaths;             % path interpretation parameter
  98. log:=1; linear:=2;
  99. Gpaths := linear;
  100.  
  101.  
  102.  
  103. %%%%%%%%%%%%%%%%%%%%%% Coordinates: setcoords, setrange %%%%%%%%%%%%%%%%%%%%%%
  104.  
  105. % Graph-related usr input is `user graph coordinates' as specified by arguments
  106. % to setcoords.
  107. % `Internal graph coordinates' are used for Gcur_, Gcbb_, Z_.low, Z_.high.
  108. % Their meaning depends on the appropriate component of Z_.ctyp:
  109. % log means internal graph coords = Mlog(user graph coords)
  110. % -log means internal graph coords = -Mlog(user graph coords)
  111. % linear means internal graph coords = Mexp(Mlog(user graph coords) Mdiv ?sc)
  112. % -linear means internal graph coords = -Mexp(Mlog(user graph coords) Mdiv ?sc)
  113. % (In the last two lines, `?sc' means X_.sc or Y_.sc as appropriate.)
  114.  
  115.  
  116. vardef Gsetp_ =         % Set default Z_.low, Z_.high
  117.   forsuffixes $=low,high:
  118.     (if known X_$: whatever else: X_$ fi, if known Y_$: whatever else: Y_$ fi)
  119.         = Gmargin$[llcorner Gcbb_,urcorner Gcbb_] + Gmarg_$;
  120.   endfor
  121. enddef;
  122. pair Gmarg_.low, Gmarg_.high;
  123. Gmarg_.high=-Gmarg_.low=(.00002,.00002);
  124.  
  125.  
  126. % Set $, $$, $$$ so that shifting by $ then transforming by $$ and then $$$
  127. % maps the essential bounding box of Gcur_ into (0,0)..Z_.gdim.  The
  128. % `essential bounding box' is either what Z_.low and Z_.high imply or the
  129. % result of ignoring pen widths in Gcur_.
  130. vardef Gsetsc_(suffix $,$$,$$$) =
  131.   save p_;
  132.   Gsetp_;
  133.   pair p_, $; $=Gboff_-Z_.low;
  134.   p_ = (max(X_.high-X_.low,.9), max(Y_.high-Y_.low,.9));
  135.   transform $$, $$$;
  136.   forsuffixes #=$$,$$$: xpart#=ypart#=xypart#=yxpart#=0; endfor
  137.   (Z_.high+Gboff_+$) transformed $$ = p_;
  138.   p_ transformed $$$ = Z_.gdim;
  139. enddef;
  140. Gmargin.low=-.07;                       % bbox fraction for default range start
  141. Gmargin.high=1.07;                      % bbox fraction for default range stop
  142. pair Gboff_; Gboff_=epsilon*(3,3);      % allowance to avoid numerical trouble
  143.  
  144.  
  145. def Gwithpc_(expr q) =
  146.   withpen penpart q withcolor (redpart q, greenpart q, bluepart q)
  147. enddef;
  148.  
  149.  
  150. % Add picture component q to picture @# and change part p to tp, where p is
  151. % something from q that needs coordinate transformation.  The type of p is pair
  152. % or path.
  153. % Pair o is the value of p that makes tp (0,0).  This implements the trick
  154. % whereby using 1 instead of 0 for th the width or height or the setbounds path
  155. % for a label picture supresses shifting in x or y.
  156. vardef Gpconv_@#(expr q, o)(text tp) =
  157.   save p;
  158.   if stroked q:
  159.     path p; p=pathpart q;
  160.     addto @# doublepath tp Gwithpc_(q) dashed dashpart q;
  161.   elseif filled q:
  162.     path p; p=pathpart q;
  163.     addto @# contour tp Gwithpc_(q);
  164.   else:
  165.     interim truecorners:=0;
  166.     pair p; p=llcorner q;
  167.     if urcorner q<>p: p:=p+Gcmul_(o-p,urcorner q-p); fi
  168.     addto @# also q shifted ((tp)-llcorner q);
  169.   fi
  170. enddef;
  171. def Gcmul_(expr a,b) = (xpart a*xpart b, ypart a*ypart b) enddef;
  172.  
  173.  
  174. vardef Gclbnds_@# =  numeric @#.low, @#.high;  enddef;
  175.  
  176.  
  177. % Finalize anything drawn in the present coordinate system and set up a new
  178. % system as requested
  179. vardef setcoords(expr tx, ty) =
  180.   interim warningcheck:=0;
  181.   if length Gcur_>0:
  182.     save s, S, T;
  183.     Gsetsc_(s, S, T);
  184.     for q within Gcur_:
  185.       Gpconv_.Gfin_(q, -s, p shifted s transformed S transformed T);
  186.     endfor
  187.     Gcur_ := Gcbb_ := nullpicture;
  188.   fi
  189.   Gclbnds_.X_; Gclbnds_.Y_;
  190.   X_.ctyp:=tx; Y_.ctyp:=ty;
  191. enddef;
  192.  
  193.  
  194. % Use scaling command cc to rescale everything in internal graph coords so that
  195. % if Mlog(user graph coords) is u then the internal graph coord value becomes
  196. % 10000/128.  Assume u>=$sc+4Mten where $ is X_ or Y_, depending on whether cc
  197. % is xscaled or yscaled.
  198. vardef Glinsc_@#(expr u)(text cc) =
  199.   save v, P;
  200.   v = mexp(4Mten + (@#sc-u));
  201.   picture P; P=nullpicture;
  202.   for q within Gcur_: Gpconv_.P(q, origin, p cc v cc 1/128); endfor
  203.   Gcur_ := P;
  204.   Gcbb_ := Gcbb_ cc v cc 1/128;
  205.   forsuffixes $=low, high:
  206.     if known @#.$: @#.$:=@#.$*v/128; fi
  207.   endfor
  208.   @#sc:= Mabs u -1115.72742;  % @#sc:=Mabs u+Mlog(128)-4Mten
  209.   Gdidsc_ := true;
  210. enddef;
  211.  
  212.  
  213. % Convert x coordinate u from Mlog(user graph coords) to ctyp=linear internal
  214. % graph coords.  If the result would be uncomfortably large, use Glinsc_ to
  215. % descale as needed.
  216. vardef Gxcvlin_ primary u =
  217.   interim warningcheck:=0;
  218.   if unknown u: u
  219.   elseif u>X_.sc+4Mten:
  220.     Glinsc_.X_(u,xscaled);
  221.     78.125
  222.   else: Mexp(u Mdiv X_.sc)
  223.   fi
  224. enddef;
  225.  
  226. vardef Gycvlin_ primary u =     % same as Gxcvlin_ but u is a y coordinate
  227.   interim warningcheck:=0;
  228.   if unknown u: u
  229.   elseif u>Y_.sc+4Mten:
  230.     Glinsc_.Y_(u,yscaled);
  231.     78.125
  232.   else: Mexp(u Mdiv Y_.sc)
  233.   fi
  234. enddef;
  235.  
  236.  
  237. % Set Z_.low and Z_.high to correspond to given range of user graph
  238. % coordinates.  The text argument should be a sequence of pairs and/or strings
  239. % with 4 components in all.
  240. vardef setrange(text t) =
  241.   interim warningcheck:=0;
  242.   save r_; r_=0;
  243.   string r_[]s;
  244.   for x_=
  245.       for p_=t: if pair p_: xpart p_, ypart fi p_, endfor:
  246.     r_[incr r_] if string x_: s fi = x_;
  247.     if r_>2:
  248.       Gsetr_ if r_=3: X_(Gxcvlin_) else: Y_(Gycvlin_) fi(
  249.           r_[r_-2] if unknown r_[r_-2]: s fi, x_);
  250.     fi
  251.     exitif r_=4;
  252.   endfor
  253. enddef;
  254.  
  255.  
  256. % @# is X_ or Y_; $ is Gxcvlin_ or Gycvlin_; l and h are numeric or string
  257. vardef Gsetr_@#(suffix $)(expr l, h) =
  258.   Gclbnds_@#;
  259.   if @#ctyp>0: (@#low, @#high) else: -(@#high, @#low) fi
  260.   = if abs @#ctyp=log: (Mlog_Str l, Mlog_Str h)
  261.     else:  ($ Mlog_Str l, $ Mlog_Str h)
  262.     fi;
  263. enddef;
  264.  
  265.  
  266.  
  267.  
  268.  
  269. %%%%%%%%%%%%%%%%%%%%%%%%% Converting path coordinates %%%%%%%%%%%%%%%%%%%%%%%%%
  270.  
  271. % Find the result of scanning path p and using macros tx and ty to adjust the
  272. % x and y parts of each coordinate pair.  Boolean paramter c tells whether to
  273. % force the result to be polygonal.
  274. vardef Gscan_(expr p, c)(suffix tx, ty) =
  275.   if (str tx="") and (str ty=""):  p
  276.   else:
  277.     save r_; path r_;
  278.     forever:
  279.       Gdidsc_ := false;
  280.       r_ := Gpp_(point 0 of p, tx, ty)
  281.       if path p:
  282.         for t=1 upto length p:
  283.           if c: --
  284.           else: ..controls Gpp_(postcontrol(t-1) of p, tx, ty)
  285.             and Gpp_(precontrol t of p, tx, ty) ..
  286.           fi
  287.           Gpp_(point t of p, tx, ty)
  288.         endfor
  289.         if cycle p: &cycle fi
  290.       fi;
  291.       exitunless Gdidsc_;
  292.     endfor
  293.     if pair p: point 0 of fi r_
  294.   fi
  295. enddef;
  296. vardef Gpp_(expr p)(suffix tx, ty) = (tx xpart p, ty ypart p) enddef;
  297.  
  298.  
  299. % Convert path p from Mlog(user graph coords) to internal graph coords.
  300. % Boolean flag f says whether to force the result to be polygonal.
  301. vardef GMcvi_(expr f) primary p =
  302.   Gscan_(p, f,
  303.       if abs X_.ctyp=linear: Gxcvlin_ fi,
  304.       if abs Y_.ctyp=linear: Gycvlin_ fi)
  305.     if X_.ctyp<0:  xscaled -1  fi
  306.     if Y_.ctyp<0:  yscaled -1  fi
  307. enddef;
  308.  
  309.  
  310. % Convert path p from user graph coords to internal graph coords.
  311. vardef Gucvi_ primary p =
  312.   if Gpaths=log:
  313.     GMcvi_((abs X_.ctyp<>log) or (abs Y_.ctyp<>log)) p
  314.   else:
  315.     interim warningcheck:=0;
  316.     save t, u;
  317.     t=Mexp(-X_.sc); u=Mexp(-Y_.sc);
  318.     Gscan_(p, (abs X_.ctyp<>linear) or (abs Y_.ctyp<>linear),
  319.         if abs X_.ctyp=log: Mlog fi,
  320.         if abs Y_.ctyp=log: Mlog fi)
  321.       transformed  (identity
  322.         if abs X_.ctyp=linear:  xscaled t  fi
  323.         if abs Y_.ctyp=linear:  yscaled u  fi
  324.         if X_.ctyp<0:  xscaled -1  fi
  325.         if Y_.ctyp<0:  yscaled -1  fi)
  326.   fi
  327. enddef;
  328.  
  329.  
  330. % Convert label location t_ from user graph coords to internal graph coords.
  331. % The label location should be a pair, or two numbers/strings.  If t_ is empty
  332. % or a single item of non-pair type, just return t_.  Unknown coordinates
  333. % produce unknown components in the result.
  334. vardef Gtcvi_(text t_) =
  335.   save n_; n_=0;
  336.   interim warningcheck:=0;
  337.   if 0 for x_=t_: +1 if pair x_: +1 fi endfor <= 1:
  338.     t_
  339.   else:
  340.     n_0 = n_1 = 0;
  341.     point 0 of GMcvi_(true) (
  342.       for x_=
  343.         for y_=t_: if pair y_: xpart y_, ypart fi y_, endfor
  344.         0, 0:
  345.         if known x_: Mlog_Str x_
  346.         else: hide(n_[n_]:=whatever) Mzero
  347.         fi
  348.         exitif incr n_=2;
  349.       ,endfor) + (n_0,n_1)
  350.   fi
  351. enddef;
  352.  
  353.  
  354.  
  355. %%%%%%%%%%%%%%%%%%%%%%%%%%%%% Reading data files %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  356.  
  357. % Read a line from file f, extract whitespace-separated tokens ignoring any
  358. % initial "%", and return true if at least one token is found.  The tokens
  359. % are stored in @#1, @#2, .. with "" in the last @#[] entry.
  360. vardef Grdln_@#(expr f) =
  361.   save n_, s_; string s_;
  362.   s_ = readfrom f;
  363.   string @#[];
  364.   if s_<>EOF:
  365.     @#1 := loptok s_;
  366.     n_ = if @#1="%": 0 else: 1 fi;
  367.     forever:
  368.       @#[incr n_] := loptok s_;
  369.       exitif @#[n_]="";
  370.     endfor
  371.     @#1<>""
  372.   else: false
  373.   fi
  374. enddef;
  375.  
  376.  
  377. % Execute c for each line of data read from file f, and stop at the first
  378. % line with no data.  Commands c can use line number i and tokens $1, $2, ...
  379. def gdata(expr f)(suffix $)(text c) =
  380.   for i=1 upto infinity:
  381.     exitunless Grdln_$(f);
  382.     c
  383.   endfor
  384. enddef;
  385.  
  386.  
  387. % Read a path from file f and return it in Mlog form.  The path is terminated
  388. % by blank line or EOF.
  389. vardef Mreadpath(expr f) =
  390.   interim warningcheck:=0;
  391.   save s;
  392.   gdata(f, s, if i>1:--fi
  393.       if s2="": (Mlog i, Mlog_str s1)
  394.       else: (Mlog_str s1, Mlog_str s2) fi)
  395. enddef;
  396.  
  397.  
  398. % Append coordinates t to polygonal path @#.  The coordinates can be numerics,
  399. % strings, or a single pair.
  400. vardef augment@#(text t) =
  401.   interim warningcheck := 0;
  402.   if not path begingroup @# endgroup:
  403.     Gerr(begingroup @# endgroup, "Cannot augment--not a path");
  404.   else:
  405.     def Gcma_= hide(def Gcma_=,enddef) enddef;
  406.     if known @#:  @#:=@#--  else:  @#=  fi
  407.     (for p=t:
  408.        Gcma_ if string p: Mexp Mlog_str fi p
  409.      endfor);
  410.   fi
  411. enddef;
  412.  
  413.  
  414.  
  415. %%%%%%%%%%%%%%%%%%%%%%%%%%%%% Drawing and filling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  416.  
  417. % Unknown pair components are set to 0 because glabel and gdotlabel understand
  418. % unknown coordinates as `0 in absolute units'.
  419. vardef Gupbb_(expr p) =
  420.   if known p: addto Gcbb_ doublepath p;
  421.   else:
  422.     save x,y;
  423.     z = llcorner Gcbb_;
  424.     if unknown xpart p: xpart p= else: x:= fi 0;
  425.     if unknown ypart p: ypart p= else: y:= fi 0;
  426.     addto Gcbb_ doublepath (p+z);
  427.   fi
  428.   Gcbb_ := image(fill llcorner Gcbb_..urcorner Gcbb_--cycle);
  429. enddef;
  430.  
  431.  
  432. % Initiate a gdraw or gfill command.  This must be done before scanning the
  433. % argument, because that could invoke the `if known Gppic_' test in a following
  434. % plot option .
  435. def Gaddto_ = Gdpic_:=Gppic_:=nullpicture; addto Gdpic_ enddef;
  436.  
  437.  
  438. % Handle the part of a Gdraw command that uses path or data file p.
  439. def Gdraw_ expr p =
  440.   if string p: GMcvi_(true) Mreadpath(p)
  441.   elseif path p or pair p: Gucvi_ p
  442.   else: Gerr_(p,"gdraw argument should be a data file or a path")
  443.         origin
  444.   fi
  445.   withpen currentpen Gwithlist_ _op_
  446. enddef;
  447.  
  448.  
  449. % Handle the part of a Gdraw command that uses path or data file p.
  450. def Gfill_ expr p =
  451.   if string p: GMcvi_(true) Mreadpath(p) --cycle
  452.   elseif cycle p: Gucvi_ p
  453.   else: Gerr_(p,"gfill argument should be a data file or a cyclic path")
  454.         origin..cycle
  455.   fi Gwithlist_ _op_
  456. enddef;
  457.  
  458. def gdraw = Gaddto_ doublepath Gdraw_ enddef;
  459. def gfill = Gaddto_ contour Gfill_ enddef;
  460.  
  461.  
  462. % This is used in Gdraw_ and Gfill_ to allow postprocessing Gdpic_
  463. def Gwithlist_ text t_ = t_; Gpostdr_; enddef;
  464.  
  465.  
  466. % Set Gppic_ so the postprocessing step will plot picture p at each path knot.
  467. % Also select nullpen to supress stroking.
  468. def plot expr p =
  469.   if known Gppic_:
  470.     withpen nullpen
  471.     hide (Gppic_:=image(
  472.         if bounded p: for q within p: Gdrw_ q endfor    % Save memory
  473.         else: Gdrw_ p
  474.         fi Gsetb_ origin..cycle))
  475.   fi
  476. enddef;
  477.  
  478. % This hides a semicolon that could prematurely end Gwithlist_'s text argument
  479. def Gdrw_ primary p = addto currentpicture also p; enddef;
  480. def Gsetb_ = setbounds currentpicture to enddef;
  481.  
  482.  
  483. def gdrawarrow = Garw_:=1; gdraw enddef;
  484. def gdrawdblarrow = Garw_:=2; gdraw enddef;
  485.  
  486.  
  487. % Post-process the filled or stroked picture Gdpic_ as follows: (1) update
  488. % the bounding box information; (2) transfer it to Gcur_ unless the pen has
  489. % been set to nullpen to disable stroking; (3) plot Gppic at each knot.
  490. vardef Gpostdr_ =
  491.   save p;
  492.   path p; p=pathpart Gdpic_;
  493.   Gupbb_(p);
  494.   if filled Gdpic_ or not Gisnull_(penpart Gdpic_):
  495.     addto Gcur_ also Gdpic_;
  496.   fi
  497.   if length Gppic_>0:
  498.     for i=0 upto length p if cycle p: -1 fi:
  499.       addto Gcur_ also Gppic_ shifted point i of p;
  500.     endfor
  501.     picture Gppic_;
  502.   fi
  503.   if Garw_>0:
  504.     Garwhd_(p, Gwithpc_(Gdpic_));
  505.     if Garw_>1: Garwhd_(reverse p, Gwithpc_(Gdpic_)); fi
  506.     Garw_:=0;
  507.   fi
  508. enddef;
  509. vardef Gisnull_(expr p) = (urcorner p=origin) and (llcorner p=origin) enddef;
  510.  
  511.  
  512. vardef Garwhd_(expr p)(text w) =        % Draw arrowhead for path p, with list w
  513.   addto Gcur_ also
  514.     image(draw arrowhead p w; Gsetb_ point infinity of p..cycle);
  515. enddef;
  516.  
  517.  
  518.  
  519. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Drawing labels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  520.  
  521. % Argument c is a drawing command that needs an additonal argument p that gives
  522. % a location in internal graph coords.  Draw in Gcur_ enclosed in a setbounds
  523. % path.  Unknown components of p cause the setbounds path to have width or
  524. % height 1 instead of 0.  Then Gupbb_ sets these components to 0 and Gpconv_
  525. % supresses subsequent repositioning.
  526. def Glab_(expr p)(suffix $)(text c) =
  527.   save sdim_; pair sdim_;
  528.   sdim_ := (if unknown xpart p: 1+ fi 0, if unknown ypart p: 1+ fi 0);
  529.   Gupbb_(p);
  530.   addto Gcur_ also
  531.     image(c(p); Gsetb_ p--p+sdim_--cycle) _op_
  532. enddef;
  533.  
  534.  
  535. % Stash the result drawing command c in the Ggl_ table using with list w and
  536. % an index based on angle laboff$.
  537. vardef Gglab_(suffix $)(text c) text w =
  538.   Ggl_[1.5+angle laboff$ /90] = image(c(origin) w);
  539. enddef;
  540.  
  541.  
  542. def Glloc_ primary p =
  543.   if pair p: Glab_(p)
  544.   elseif numeric p: Glab_(point p of pathpart Gdpic_)
  545.   else: Gglab_
  546.   fi
  547. enddef;
  548.  
  549.  
  550. % Place label p at user graph coords t using with list w. (t is a time, a pair
  551. % or 2 numerics or strings).
  552. vardef glabel@#(expr p)(text t) text w  =
  553.   Glloc_ Gtcvi_(t)  (@#,label@#(p)) w; enddef;
  554.  
  555.  
  556. % Place label p at user graph coords t using with list w and draw a dot there.
  557. % (t is a time, a pair, or 2 numerics or strings).
  558. vardef gdotlabel@#(expr p)(text t) text w =
  559.   Glloc_ Gtcvi_(t)  (@#,dotlabel@#(p)) w; enddef;
  560.  
  561.  
  562. def OUT = enddef;       % location text for outside labels
  563.  
  564.  
  565.  
  566. %%%%%%%%%%%%%%%%%%%%%%%%%% Grid lines, ticks, etc. %%%%%%%%%%%%%%%%%%%%%%%%%%
  567.  
  568. % Grid lines and tick marks are transformed versions of the templates below.
  569. % In the template paths, (0,0) is on the edge of the frame and inward is to
  570. % the right.
  571. path Gtemplate.itick, Gtemplate.otick, Gtemplate.grid;
  572. Gtemplate.itick = origin--(7bp,0);
  573. Gtemplate.otick = (-7bp,0)--origin;
  574. Gtemplate.grid = origin--(1,0);
  575.  
  576. vardef itick@#(expr f,u) text w =  Gtlab_(@#,@,false,f,u,w);  enddef;
  577.  
  578. vardef otick@#(expr f,u) text w =  Gtlab_(@#,@,false,f,u,w);  enddef;
  579.  
  580. vardef grid@#(expr f,u) text w =  Gtlab_(@#,@,true,f,u,w);  enddef;
  581.  
  582.  
  583. % Produce a tick or grid mark for label suffix $, Gtemplate suffix $$,
  584. % coordinate value u, and with list w.  Boolean c tells whether Gtemplate$$
  585. % needs scaling by X_.gdim or Y_.gdim, and f gives a format string or a label
  586. % picture.
  587. def Gtlab_(suffix $,$$)(expr c, f, u)(text w) =
  588.   Glab_(Gtcvi_(Ggpos_($,u)),,draw Ggpic_$($$,c,f,u,w) shifted)
  589. enddef;
  590.  
  591.  
  592. % Generate label positioning arguments appropriate for label suffix $ and
  593. % coordinate u.
  594. def Ggpos_(suffix $)(expr u) =
  595.   if xpart laboff.$=0: u,whatever else: whatever,u fi
  596. enddef;
  597.  
  598.  
  599. % Generate a picture of a grid line labeled with coordinate value u, picture
  600. % or format string f, and with list w.  Suffix @# is bot, top, lft, or rt,
  601. % suffix $ identifies entries in the Gtemplate table, and boolean c tells
  602. % whether to scale Gtemplate$.
  603. vardef Ggpic_@#(suffix $)(expr c, f, u)(text w) =
  604.   if unknown u: Gerr_(u,"Label coordinate should be known"); nullpicture
  605.   else:
  606.     save p; path p;
  607.     interim warningcheck:=0;
  608.     Gneedgr_:=false;
  609.     p = Gtemplate$ zscaled -laboff@#
  610.         if c: Gxyscale fi
  611.         shifted (((.5 + laboff@# dotprod (.5,.5)) * laboff@#) Gxyscale);
  612.     image(draw p w;
  613.         label@#(if string f: format(f,u) else: f fi, point 0 of p))
  614.   fi
  615. enddef;
  616. def Gxyscale = xscaled X_.gdim yscaled Y_.gdim enddef;
  617.  
  618.  
  619. % Draw the frame or the part corresponding to label suffix @# using with list w.
  620. vardef frame@# text w =
  621.   Gneedfr_:=false;
  622.   picture p_;
  623.   p_ = image(draw
  624.     if str@#<>"":  subpath round(angle laboff@#*Gfra_+Gfrb_) of  fi
  625.     unitsquare Gxyscale  w);
  626.   Glab_((whatever,whatever),,draw p_ shifted);
  627. enddef;
  628. pair Gfra_; Gfra_=(1,1)/90;     % unitsquare subpath is linear in label angle
  629. pair Gfrb_; Gfrb_=(.75,2.25);
  630.  
  631.  
  632.  
  633.  
  634. %%%%%%%%%%%%%%%%%%%%%%%%%% Automatic grid selection %%%%%%%%%%%%%%%%%%%%%%%%%%
  635.  
  636. string Glmarks[];       % marking options per decade for logarithmic scales
  637. string Gumarks;         % mark spacing options per decade for linear scales
  638. string Gemarks;         % exponent spacing options for logarithmic scales
  639. newinternal Gmarks, Gminlog;
  640. Gmarks := 4;            % minimum number marks generated by auto.x or auto.y
  641. Gminlog := 3.0;         % revert to uniform marks when largest/smallest < this
  642.  
  643. def Gfor(text t) = for i=t endfor enddef;  % to shorten the mark templates below
  644. Glmarks[1]="1,2,5";
  645. Glmarks[2]="1,1.5,2,3,4,5,7";
  646. Glmarks[3]="1Gfor(6upto10:,i/5)Gfor(5upto10:,i/2)Gfor(6upto9:,i)";
  647. Glmarks[4]="1Gfor(11upto20:,i/10)Gfor(11upto25:,i/5)Gfor(11upto19:,i/2)";
  648. Glmarks[5]="1Gfor(21upto40:,i/20)Gfor(21upto50:,i/10)Gfor(26upto49:,i/5)";
  649. Gumarks="10,5,2";       % start with 10 and go down; a final `,1' is appended
  650. Gemarks="20,10,5,2,1";
  651.  
  652.  
  653. % Determine the X_ or Y_ bounds on the range to be covered by automatic grid
  654. % marks.  Suffix @# is X_ or Y_.  The result is log or linear to specify the
  655. % type of grid spacing to use.  Bounds are returned in variables local to
  656. % begingraph..endgraph: pairs Gmll_ and Gmhh_ are upper and lower bounds in
  657. % `modified exponential form'.  In modified exponential form, (x,y) means
  658. % (x/1000)*10^y, where 1000<=abs x<10000.
  659. vardef Gpick_@# =
  660.   interim warningcheck:=0;
  661.   save l, h;
  662.   Gsetp_;
  663.   if @#ctyp>0: (l,h) else: -(h,l) fi = (@#low, @#high);
  664.   if abs @#ctyp=log:
  665.     Gmll_ := Meform(Mabs l)+Gbias_;
  666.     Gmhh_ := Meform(Mabs h)+Gbias_;
  667.     if h-l >=mlog Gminlog: log else: linear fi
  668.   else:
  669.     Gmll_ := Meform(@#sc + Mlog l)+Gbias_;
  670.     Gmhh_ := Meform(@#sc + Mlog h)+Gbias_;
  671.     linear
  672.   fi
  673. enddef;
  674. pair Gbias_; Gbias_=(0,3);
  675. pair Gmll_, Gmhh_;
  676.  
  677.  
  678. % Scan Glmarks[k] and evaluate tokens t for each m where l<=m<=h.
  679. def Gmsc_(expr k, l, h)(text t) =
  680.   for m=scantokens Glmarks[k]:
  681.     exitif m>h;
  682.     if m>=l: t fi
  683.   endfor
  684. enddef;
  685.  
  686.  
  687. % Scan Gmark[k] and evaluate tokens t for each m and e where m*10^e belongs
  688. % between l and h (inclusive), where both l and h are in modified exponent form.
  689. def Gmscan_(expr k, l, h)(text t) =
  690.   for e=ypart l upto ypart h:
  691.     Gmsc_(k, if e>ypart l: 1 else: xpart l/1000 fi,
  692.         if e<ypart h: 10 else: xpart h/1000 fi,  t)
  693.   endfor
  694. enddef;
  695.  
  696.  
  697. % Select a k for which Gmscan_(k,...) gives enough marks.
  698. vardef Gkpick_ =
  699.   save k;
  700.   k = 0;
  701.   forever:
  702.     exitif unknown Glmarks[k+1];
  703.     exitif 0 Gmscan_(incr k, Gmll_, Gmhh_, +1) >= Gmarks;
  704.   endfor
  705.   k
  706. enddef;
  707.  
  708.  
  709. % Try to select an exponent spacing from Gemarks.  If successful, set @# and
  710. % return true
  711. vardef Gempick_@# =
  712.   numeric @#;
  713.   for e=scantokens Gemarks:
  714.     @# = e;
  715.     exitif floor(ypart Gmhh_/e)-floor(Gey_(Gmll_)/e) >= Gmarks;
  716.     numeric @#;
  717.   endfor
  718.   known @#
  719. enddef;
  720.  
  721. vardef Gey_(expr p) = ypart p  if xpart p=1000: -1 fi  enddef;
  722.  
  723.  
  724. % Compute the mark spacing d between xpart Gmll_ and xpart Gmhh_.
  725. vardef Gipick_ =
  726.   interim warningcheck:=0;
  727.   save m, n, d;
  728.   m = Gmarks;
  729.   n = 1 for i=1 upto mlog(xpart Gmhh_-xpart Gmll_)/Mten - mlog m/(Mten-epsilon):
  730.         *10 endfor;
  731.   if n<=1000:
  732.     for x=scantokens Gumarks:
  733.       d = n*x;
  734.       exitif 0 Gigen_(d,+1)>=m;
  735.       numeric d;
  736.     endfor
  737.   fi
  738.   if known d: d else: n fi
  739. enddef;
  740.  
  741.  
  742. def Gigen_(expr d)(text t) =
  743.   for m = d*ceiling(xpart Gmll_/d) step d until xpart Gmhh_:
  744.     t
  745.   endfor
  746. enddef;
  747.  
  748.  
  749. % Evaluate tokens t for exponents e in multiples of d in the range determined
  750. % by Gmll_ and Gmhh_.
  751. def Gemgen_(expr d)(text t) =
  752.   for e = d*floor(Gey_(Gmll_)/d+1)
  753.       step d until d*floor(ypart Gmhh_/d):  t
  754.   endfor
  755. enddef;
  756.  
  757.  
  758. % Adjust Gmll_ and Gmhh_ so their exponent parts match and they are in true
  759. % exponent form ((x,y) means x*10^y).  Return the new exponent.
  760. vardef Gesame_ =
  761.   interim warningcheck := 0;
  762.   save e;
  763.   e+3 = if Gmll_=Gbias_: ypart Gmhh_
  764.         elseif Gmhh_=Gbias_: ypart Gmll_
  765.         else: max(ypart Gmll_, ypart Gmhh_) fi;
  766.   forsuffixes $=Gmll_, Gmhh_:
  767.     $ := (xpart $ for i=ypart $ upto e+2: /(10) endfor, e);
  768.   endfor
  769.   e
  770. enddef;
  771.  
  772.  
  773. % Assume e is an integer and either m=0 or 1<=abs(m)<10000.  Find m*(10^e)
  774. % and represent the result as a string if its absolute value would be at least
  775. % 4096 or less than .1.  It is OK to return 0 as a string or a numeric.
  776. vardef Gpack_(expr m, e) =
  777.   if (e>3)or(e<-4):
  778.     decimal m & "e" & decimal e
  779.   elseif e>=0:
  780.     if abs m<infinity/Ten_to[e]:
  781.           m*Ten_to[e]
  782.     else: decimal m & "e" & decimal e
  783.     fi
  784.   else:
  785.     save x; x=m/Ten_to[-e];
  786.     if abs x>=.1: x else: decimal m & "e" & decimal e fi
  787.   fi
  788. enddef;
  789.  
  790.  
  791. def auto suffix $ =
  792.   hide(def Gcma_= hide(def Gcma_=,enddef) enddef)
  793.   if Gpick_.Gsuf_($)=log:
  794.     if Gempick_.Gme_:  Gemgen_(Gme_, Gcma_ Gpack_(1,e))
  795.     else: 
  796.       Gmscan_(Gkpick_, Gmll_, Gmhh_, Gcma_ Gpack_(m,e))
  797.     fi
  798.   else:
  799.     hide(Gme_:=Gesame_)
  800.     Gigen_(Gipick_, Gcma_ Gpack_(m,Gme_))
  801.   fi
  802. enddef;
  803.  
  804.  
  805. string Autoform; Autoform = "%g";
  806.  
  807. vardef autogrid(suffix tx, ty) text w =
  808.   Gneedgr_:=false;
  809.   if str tx<>"": for x=auto.x: tx(Autoform,x) w; endfor fi
  810.   if str ty<>"": for y=auto.y: ty(Autoform,y) w; endfor fi
  811. enddef;
  812.  
  813.  
  814.  
  815. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% endgraph %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  816.  
  817. def endgraph =
  818.   if Gneedgr_: autogrid(otick.bot, otick.lft); fi
  819.   if Gneedfr_: frame; fi
  820.   setcoords(linear,linear);
  821.   interim truecorners:=1;
  822.   for b=bbox Gfin_:
  823.     setbounds Gfin_ to b;
  824.     for i=0 step .5 until 3.5:
  825.       if known Ggl_[i]: addto Gfin_ also Ggl_[i] shifted point i of b; fi
  826.     endfor
  827.   endfor
  828.   Gfin_
  829.   endgroup
  830. enddef;
  831.